#include <ext/stdio_filebuf.h>
#include <fstream>
#include <iostream>
#include <sstream>
#include <stdlib.h>

#include "configuration.h"
#include "predicates.h"
#include "switcher.h"
#include "system.h"
#include "tuple.h"

// Stream bases are functional construction part of a stream. Real ones are
//  created using multiple inheritance of many bases

class Stream {
public:
  virtual ~Stream () {
  }

  virtual bool put (const std::string&) {
    return false;
  }

  virtual bool flush () {
    return false;
  }

  virtual bool get (char&) {
    return false;
  }

  virtual bool getLine (std::string&) {
    return false;
  }

  virtual bool peek (char&) {
    return false;
  }

  virtual bool seek (int) {
    return false;
  }

  virtual bool eof () {
    return true;
  }

  virtual bool close () {
    return true;
  }
};

template <class S>
class InputStreamBase : virtual public Stream {
public:
  InputStreamBase (S is) :
    stream (is) {
  }

  bool get (char &c) {
    return stream && !stream->eof () && !stream->bad () && (c = stream->get ())
        != EOF;
  }

  bool getLine (std::string &buffer) {
    bool ret = stream && !stream->eof () && !stream->bad ();
    ret && getline (*stream, buffer);
    return ret && *stream;
  }

  bool peek (char &c) {
    return stream && !stream->eof () && !stream->bad ()
        && (c = stream->peek ()) != EOF;
  }

  virtual bool seek (int p) {
    return stream->seekg (p, std::ios::cur);
  }

  bool eof () {
    return stream->eof ();
  }

  S stream;
};

typedef InputStreamBase<std::istream*> InputStream;

template <class S>
class OutputStreamBase : virtual public Stream {
public:
  OutputStreamBase (S os) :
    stream (os) {
  }

  bool put (const std::string &buffer) {
    if(stream) {
      stream->write (buffer.c_str (), buffer.length ());
      return !stream->bad ();
    } else
      return false;
  }

  virtual bool seek (int p) {
    return stream && stream->seekp (p, std::ios::cur);
  }

  virtual bool flush () {
    return stream && stream->flush ();
  }

  S stream;
};

typedef OutputStreamBase<std::ostream*> OutputStream;

template <class S>
class ClosableStreamBase : virtual public Stream {
public:
  ClosableStreamBase (S s) :
    stream (s) {
  }

  virtual bool close () {
    if(stream)
      stream->close ();
    return true; // Would this ever fails?
  }

  S stream;
};

template <class S>
class DtorStreamBase : virtual public Stream {
public:
  DtorStreamBase (S s) :
    stream (s) {
  }
  ~DtorStreamBase () {
    delete stream;
  }

  S stream;
};

template <class S>
class InputClosableDeletableStream : public InputStreamBase<S> ,
    public ClosableStreamBase<S> ,
    public DtorStreamBase<S> {
public:
  InputClosableDeletableStream (S s) :
    InputStreamBase<S> (s), ClosableStreamBase<S> (s), DtorStreamBase<S> (s) {
  }
};

typedef InputClosableDeletableStream<std::ifstream*> InputFileStream;

template <class S>
class OutputClosableDeletableStream : public OutputStreamBase<S> ,
    public ClosableStreamBase<S> ,
    public DtorStreamBase<S> {
public:
  OutputClosableDeletableStream (S s) :
    OutputStreamBase<S> (s), ClosableStreamBase<S> (s), DtorStreamBase<S> (s) {
  }
};

typedef OutputClosableDeletableStream<std::ofstream*> OutputFileStream;

template <class S>
class IODeletableStream : public InputStreamBase<S> ,
    public OutputStreamBase<S> ,
    public DtorStreamBase<S> {
public:
  IODeletableStream (S s) :
    InputStreamBase<S> (s), OutputStreamBase<S> (s), DtorStreamBase<S> (s) {
  }

  bool seek (int p) {
    return InputStreamBase<S>::seek (p);
  }
};
typedef IODeletableStream<std::stringstream*> BufferStream;

// This has to combine separate input/output streams
class PipeStream : public InputStreamBase<std::istream*> ,
    public OutputStreamBase<std::ostream*> {
public:
  typedef InputStreamBase<std::istream*> isBase;
  typedef OutputStreamBase<std::ostream*> osBase;

  PipeStream (__gnu_cxx ::stdio_filebuf<char> *ib, __gnu_cxx ::stdio_filebuf<
      char> *ob) :
    isBase (new std::istream (ib)), osBase (new std::ostream (ob)),
        inputBuffer (ib), outputBuffer (ob) {
  }
  virtual ~PipeStream () {
    close ();
  }

  // To get rid of "no unique override" error; rare to seek in a pipe anyway
  bool seek (int p) {
    return isBase::seek (p);
  }

  // Tells that there are no more read/write to the pipe
  bool close () {
    if(isBase::stream) {
      delete isBase::stream;
      isBase::stream = 0;
    }
    if(osBase::stream) {
      delete osBase::stream;
      osBase::stream = 0;
    }
    // ::close (inputBuffer->fd ()); // Does not work
    if(inputBuffer)
      delete inputBuffer;
    if(outputBuffer)
      delete outputBuffer;
    inputBuffer = outputBuffer = 0;
    return true;
  }

  __gnu_cxx ::stdio_filebuf<char> *inputBuffer;
  __gnu_cxx ::stdio_filebuf<char> *outputBuffer;
};

class streamReadFile : public OnceBindTupleProver {
predicateName("stream.readFile")

  bool proveOnce () {
    std::ifstream *stream = new std::ifstream (
        tuple->nodes[2].display ().c_str ());
    if(*stream) {
      system->globals.streamMap[tuple->nodes[1].final ()]
          = new InputFileStream (stream);
      return true;
    } else {
      delete stream;
      return false;
    }
  }
};

ProverFactoryRegistrar<streamReadFile, Tuple::ID, 3> streamReadFileRegistrar;

class streamStdin : public OnceBindTupleProver {
predicateName("stream.stdin")

  bool proveOnce () {
    system->globals.streamMap[tuple->nodes[1].final ()] = new InputStream (
        &std::cin);
    return true;
  }
};

ProverFactoryRegistrar<streamStdin, Tuple::ID, 2> streamStdinRegistrar;

pid_t pipeOpenIO (const char*, int*, int*);

class streamPipeOpen : public OnceBindTupleProver {
predicateName("stream.popen")

  bool proveOnce () {
    int infd, outfd;
    if(pipeOpenIO (tuple->nodes[2].display ().c_str (), &infd, &outfd) >= 0) {
      system->globals.streamMap[tuple->nodes[1].final ()] = new PipeStream (
          new __gnu_cxx ::stdio_filebuf<char> (infd, std::ios::in),
          new __gnu_cxx ::stdio_filebuf<char> (outfd, std::ios::out));
      return true;
    } else
      return false;
  }
};

ProverFactoryRegistrar<streamPipeOpen, Tuple::ID, 3> streamPipeOpenRegistrar;

class streamWriteFile : public OnceBindTupleProver {
predicateName("stream.writeFile")

  bool proveOnce () {
    std::ofstream *stream = new std::ofstream (
        tuple->nodes[2].display ().c_str ());
    if(*stream) {
      system->globals.streamMap[tuple->nodes[1].final ()]
          = new OutputFileStream (stream);
      return true;
    } else {
      delete stream;
      return false;
    }
  }
};

ProverFactoryRegistrar<streamWriteFile, Tuple::ID, 3> streamWriteFileRegistrar;

class streamStderr : public OnceBindTupleProver {
predicateName("stream.stderr")

  bool proveOnce () {
    system->globals.streamMap[tuple->nodes[1].final ()] = new OutputStream (
        &std::cerr);
    return true;
  }
};

ProverFactoryRegistrar<streamStderr, Tuple::ID, 2> streamStderrRegistrar;

class streamStdout : public OnceBindTupleProver {
predicateName("stream.stdout")

  bool proveOnce () {
    system->globals.streamMap[tuple->nodes[1].final ()] = new OutputStream (
        &std::cout);
    return true;
  }
};

ProverFactoryRegistrar<streamStdout, Tuple::ID, 2> streamStdoutRegistrar;

class streamCreateBuffer : public OnceBindTupleProver {
predicateName("stream.createBuffer")

  bool proveOnce () {
    system->globals.streamMap[tuple->nodes[1].final ()] = new BufferStream (
        new std::stringstream ());
    return true;
  }
};

ProverFactoryRegistrar<streamCreateBuffer, Tuple::ID, 2>
    streamCreateBufferRegistrar;

class streamGet : public OnceBindTupleProver {
predicateName("stream.get")

  bool proveOnce () {
    char got[2] = " ";
    return system->globals.streamMap[tuple->nodes[1].final ()]->get (got[0])
        && binder.bind (*tuple->nodes[2], *new String (got), system->trail);
  }
};

ProverFactoryRegistrar<streamGet, Tuple::ID, 3> streamGetRegistrar;

class streamLine : public Prover {
predicateName("stream.line")

  streamLine () :
    proved (false) {
  }

  bool prove () {
    if(proved)
      binder.undo (system->trail);
    else {
      binder.capture (system->trail);
      proved = true;
    }

    std::string got;
    do {
      if(!system->globals.streamMap[tuple->nodes[1].final ()]->getLine (got))
        return false;
    } while(!binder.bind (*tuple->nodes[2], *new String (got), system->trail));
    return true;
  }

  bool proved;
  Tuple *tuple;
  Binder binder;
  System *system;
  void set (Tuple &t, System &s) {
    tuple = &t;
    system = &s;
  }
};

ProverFactoryRegistrar<streamLine, Tuple::ID, 3> streamLineRegistrar;

class streamPeek : public OnceBindTupleProver {
predicateName("stream.peek")

  bool proveOnce () {
    char got[2] = " ";
    return system->globals.streamMap[tuple->nodes[1].final ()]->peek (got[0])
        && binder.bind (*tuple->nodes[2], *new String (got), system->trail);
  }
};

ProverFactoryRegistrar<streamPeek, Tuple::ID, 3> streamPeekRegistrar;

class streamNl : public OnceProver {
predicateName("stream.nl")

  bool proveOnce () {
    return system->globals.streamMap[tuple->nodes[1].final ()]->put ("\n");
  }

  Tuple *tuple;
  System *system;
  void set (Tuple &t, System &s) {
    tuple = &t;
    system = &s;
  }
};

ProverFactoryRegistrar<streamNl, Tuple::ID, 2> streamNlRegistrar;

class streamDump : public OnceProver {
predicateName("stream.dump")

  bool proveOnce () {
    Stream &stream = *system->globals.streamMap[tuple->nodes[1].final ()];
    for(unsigned int i = 2; i < tuple->nodes.size (); i++)
      if(!stream.put (tuple->nodes[i].dump ()))
        return false;
    return true;
  }

  Tuple *tuple;
  System *system;
  void set (Tuple &t, System &s) {
    tuple = &t;
    system = &s;
  }
};

ProverFactoryRegistrar<streamDump, Tuple::ID> streamDumpRegistrar;

class streamWrite : public OnceProver {
predicateName("stream.write")

  bool proveOnce () {
    Stream &stream = *system->globals.streamMap[tuple->nodes[1].final ()];
    for(unsigned int i = 2; i < tuple->nodes.size (); i++)
      if(!stream.put (tuple->nodes[i].display () + ' '))
        return false;
    return true;
  }

  Tuple *tuple;
  System *system;
  void set (Tuple &t, System &s) {
    tuple = &t;
    system = &s;
  }
};

ProverFactoryRegistrar<streamWrite, Tuple::ID> streamWriteRegistrar;

class streamPut : public OnceProver {
predicateName("stream.put")

  bool proveOnce () {
    return system->globals.streamMap[tuple->nodes[1].final ()]->put (
        tuple->nodes[2].display ());
  }

  Tuple *tuple;
  System *system;
  void set (Tuple &t, System &s) {
    tuple = &t;
    system = &s;
  }
};

ProverFactoryRegistrar<streamPut, Tuple::ID, 3> streamPutRegistrar;

class streamFlush : public OnceProver {
predicateName("stream.flush")

  bool proveOnce () {
    return system->globals.streamMap[tuple->nodes[1].final ()]->flush ();
  }

  Tuple *tuple;
  System *system;
  void set (Tuple &t, System &s) {
    tuple = &t;
    system = &s;
  }
};

ProverFactoryRegistrar<streamFlush, Tuple::ID, 2> streamFlushRegistrar;

class streamSeek : public OnceProver {
predicateName("stream.seek")

  bool proveOnce () {
    Node &pointerNode = *tuple->nodes[2].final ();
    return pointerNode.type () == Number::ID
        && system->globals.streamMap[tuple->nodes[1].final ()]->seek (
            static_cast<Number&> (pointerNode).value);
  }

  Tuple *tuple;
  System *system;
  void set (Tuple &t, System &s) {
    tuple = &t;
    system = &s;
  }
};

ProverFactoryRegistrar<streamSeek, Tuple::ID, 3> streamSeekRegistrar;

class streamEof : public OnceProver {
predicateName("stream.eof")

  bool proveOnce () {
    return system->globals.streamMap[tuple->nodes[1].final ()]->eof ();
  }

  Tuple *tuple;
  System *system;
  void set (Tuple &t, System &s) {
    tuple = &t;
    system = &s;
  }
};

ProverFactoryRegistrar<streamEof, Tuple::ID, 2> streamEofRegistrar;

class streamClose : public OnceProver {
predicateName("stream.close")

  bool proveOnce () {
    Node *node = tuple->nodes[1].final ();
    Stream *stream = system->globals.streamMap[node];
    bool ret = stream->close ();
    system->globals.streamMap.erase (node);
    delete stream;
    return ret;
  }

  Tuple *tuple;
  System *system;
  void set (Tuple &t, System &s) {
    tuple = &t;
    system = &s;
  }
};

ProverFactoryRegistrar<streamClose, Tuple::ID, 2> streamCloseRegistrar;

pid_t pipeOpenIO (const char *command, int *infp, int *outfp) {
  const int read = 0, write = 1;

  int pipestdin[2], pipestdout[2];
  if(pipe (pipestdin) != 0 || pipe (pipestdout) != 0)
    return -1;

  pid_t pid = fork ();
  if(pid == 0) {

    // Executes the program in child process
    close (pipestdin[write]);
    dup2 (pipestdin[read], read);
    close (pipestdout[read]);
    dup2 (pipestdout[write], write);

    execl ("/bin/sh", "sh", "-c", command, (char*) 0);
    std::cerr << "[ERROR] execl() failed" << std::endl;
    exit (1);
  } else if(pid > 0) {

    // Close unused descriptors and set return values
    close (pipestdin[read]);
    close (pipestdout[write]);
    infp ? *infp = pipestdout[read] : close (pipestdout[read]);
    outfp ? *outfp = pipestdin[write] : close (pipestdin[write]);
  }

  return pid;
}
